package com.bitrate.dagger.redis.core;

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

import org.apache.commons.pool2.impl.GenericObjectPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties.Pool;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration;
import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.util.ObjectUtils;

import com.bitrate.dagger.redis.entity.LettuceEnity;
import com.bitrate.dagger.redis.entity.RedisEntity;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;

/**
 * Redis Template Db 注册器
 * 
 * 减少Redis Db切换对上下文的开销---->容器初始化直接按照需要初始化的Redis Db加载完毕 有几个Redis Db
 * 就初始化对应的RedisTemplate
 */
public class RedisRegister implements EnvironmentAware,
		ImportBeanDefinitionRegistrar {

	private static final Logger logger = LoggerFactory.getLogger(RedisRegister.class);

	private static String TEMP_KEY = "redisTemplate";

	private static Map<String, Object> registerBean = new ConcurrentHashMap<String, Object>();

	private Environment environment;

	private Binder binder;

	@Override
	public void setEnvironment(Environment environment) {
		this.environment = environment;
		this.binder = Binder.get(this.environment);
	}

	@Override
	public void registerBeanDefinitions(
			AnnotationMetadata importingClassMetadata,
			BeanDefinitionRegistry beanDefinitionRegistry) {
		RedisEntity redisEntity;
		try {
			redisEntity = binder.bind("x.redis", RedisEntity.class).get();
		} catch (NoSuchElementException e) {
			logger.error("Failed to configure x redis: 'x.redis' attribute is not specified and no embedded redis could be configured.");
			return;
		}
		boolean onPrimary = true;

		// 根据多个库实例化出多个连接池和Template
		String databaseStrArr = redisEntity.getDatabases();
		String[] databaseArr = databaseStrArr.split(",");
		for (String database : databaseArr) {
			// 单机模式
			RedisStandaloneConfiguration configuration = new RedisStandaloneConfiguration();
			configuration.setHostName(redisEntity.getHost());
			configuration.setPort(redisEntity.getPort());
			configuration.setDatabase(Integer.valueOf(database));
			String password = redisEntity.getPassword();
			if (!ObjectUtils.isEmpty(password)) {
				RedisPassword redisPassword = RedisPassword.of(password);
				configuration.setPassword(redisPassword);
			}

			// 池配置
			GenericObjectPoolConfig<Pool> genericObjectPoolConfig = new GenericObjectPoolConfig<Pool>();

			LettuceEnity lettuceEntity = redisEntity.getLettuce();
			if (lettuceEntity != null) {
				RedisProperties.Pool pool = lettuceEntity.getPool();
				if (pool != null) {
					genericObjectPoolConfig.setMaxIdle(pool.getMaxIdle());
					genericObjectPoolConfig.setMaxTotal(pool.getMaxActive());
					genericObjectPoolConfig.setMinIdle(pool.getMinIdle());
					if (pool.getMaxWait() != null) {
						genericObjectPoolConfig.setMaxWait(Duration.ofMillis(pool.getMaxWait().toMillis()));
					}
				}
			}
			Supplier<LettuceConnectionFactory> lettuceConnectionFactorySupplier = () -> {
				LettuceConnectionFactory factory = (LettuceConnectionFactory) registerBean.get("LettuceConnectionFactory" + database);
				if (factory != null) {
					return factory;
				}
				LettucePoolingClientConfigurationBuilder builder = LettucePoolingClientConfiguration.builder();
				Duration shutdownTimeout = redisEntity.getLettuce().getShutdownTimeout();
				if (shutdownTimeout == null) {
					shutdownTimeout = binder.bind("x.redis.shutdown-timeout",Duration.class).get();
				}
				if (shutdownTimeout != null) {
					builder.shutdownTimeout(shutdownTimeout);
				}
				LettuceClientConfiguration clientConfiguration = builder.poolConfig(genericObjectPoolConfig).build();
				factory = new LettuceConnectionFactory(configuration,clientConfiguration);
				registerBean.put("LettuceConnectionFactory" + database, factory);
				return factory;
			};

			LettuceConnectionFactory lettuceConnectionFactory = lettuceConnectionFactorySupplier.get();
			BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(LettuceConnectionFactory.class,lettuceConnectionFactorySupplier);
			AbstractBeanDefinition factoryBean = builder.getRawBeanDefinition();
			factoryBean.setPrimary(onPrimary);
			beanDefinitionRegistry.registerBeanDefinition("lettuceConnectionFactory" + database, factoryBean);
			// 定义RedisTemplate对象
			GenericBeanDefinition redisTemplate = new GenericBeanDefinition();
			redisTemplate.setBeanClass(RedisTemplate.class);
			redisTemplate.getPropertyValues().add("connectionFactory",lettuceConnectionFactory);
			redisTemplate.setAutowireMode(AutowireCapableBeanFactory.AUTOWIRE_BY_NAME);
			ObjectMapper mapper = new ObjectMapper();
			mapper.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
			mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL,JsonTypeInfo.As.PROPERTY);
			// 时间序列化
			JavaTimeModule timeModule = new JavaTimeModule();
			timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
			timeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
			timeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
	        timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
	        timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern("yyyy-MM-dd")));
	        timeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
			mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
			mapper.registerModule(timeModule);
			GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(mapper);
			// key采用String的序列化方式，value采用json序列化方式
			redisTemplate.getPropertyValues().add("keySerializer",new StringRedisSerializer());
			redisTemplate.getPropertyValues().add("hashKeySerializer",new StringRedisSerializer());
			redisTemplate.getPropertyValues().add("valueSerializer",jackson2JsonRedisSerializer);
			redisTemplate.getPropertyValues().add("hashValueSerializer",jackson2JsonRedisSerializer);
			// 注册Bean
			beanDefinitionRegistry.registerBeanDefinition(TEMP_KEY + database,redisTemplate);
			logger.info("Redis Template Db({}) Init Success!", database);
			if (onPrimary) {
				onPrimary = false;
			}
		}
	}
}
